package com.szh.htmltopdf.controller;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.szh.common.resp.Resp;
import com.szh.htmltopdf.model.SysRegion;
import com.szh.htmltopdf.model.YearModel;
import com.szh.htmltopdf.service.SysRegionService;
import com.szh.htmltopdf.service.YearService;
import com.szh.htmltopdf.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.szh.htmltopdf.constants.MyAppConstant.AREA_TREE_KEY;

@RestController
@RequestMapping("/year")
@Slf4j
public class YearController {


    @Resource
    private YearService yearService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    SysRegionService regionService;


    private static final String YEAR_CONTROLLER_ADD = "YearControllerAdd";
    private static final DefaultRedisScript<Long> SKCKILL_SCRIPT;

    static {
        SKCKILL_SCRIPT = new DefaultRedisScript<>();
        SKCKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SKCKILL_SCRIPT.setResultType(Long.class);
    }

    // 测试执行lua脚本
    @GetMapping("/lua")
    public String luaTest(@RequestParam Integer voucherId, @RequestParam Integer userId,
                          @RequestParam Integer orderId) {

        Long e = redisUtil.execute(SKCKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString(), orderId.toString());
        int execute = Math.toIntExact(e);
        if (execute == 0) {
            return "执行成功";
        } else if (execute == 1) {
            return "库存不足";
        } else {
            return "重复下单";
        }
    }

    /**
     * 学习 redis Stream消费者组-消息队列
     *
     * @return
     */

    @GetMapping("/hi")
    public String hello2() {
        return "hello";
    }

    @GetMapping
    public YearModel testJson(@RequestParam Long id) {
        YearModel byId = yearService.getById(id);

        return byId;
    }

    @PostMapping
    public Resp<Boolean> add(@RequestBody YearModel dto) {
        boolean save = yearService.save(dto);
        return Resp.ok(save);
    }
    
    @GetMapping
    public void tt(){
    }

    @GetMapping("/hello")
    public String hello() throws InterruptedException {
//		获取一把锁。底层就是JUC下的锁
        RLock myLock = redissonClient.getLock("myLock");
        // 加锁。是一个阻塞式等待。默认这个锁的时间是30s，（并且会自动续期（如果业务超长，业务没有执行完成，他会自动给这个锁，加上30s）
        // 不会出现死锁问题
        myLock.tryLock(10, 10, TimeUnit.SECONDS);
        try {
            log.error("加锁成功，执行业务。。。。" + Thread.currentThread().getName());
            Thread.sleep(30_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 3-解锁
            log.error("释放锁。。。。。。" + Thread.currentThread().getName());
            myLock.unlock();
        }
        return "hello";
    }

    @GetMapping("/testLimitTime")
    public Resp<String> testLimitTime() throws InterruptedException {
        String s = redisUtil.get(YEAR_CONTROLLER_ADD);
        if (StrUtil.isBlank(s)) {
            Thread.sleep(3_000);

            redisUtil.setEx(YEAR_CONTROLLER_ADD, YEAR_CONTROLLER_ADD, 1, TimeUnit.SECONDS);
            return Resp.ok("成功执行");
        } else {
            log.error("提交过快，请稍后重试");
            throw new RuntimeException("提交过快，请稍后重试");
        }
    }

    @GetMapping("/json")
    public String testJson() {

        JSONObject jsonObject = new JSONObject();
        jsonObject.set("name", "szh");
        jsonObject.set("sex", "szh");
        jsonObject.set("age", "12");

        redisUtil.set("strKey", JSONUtil.toJsonStr(jsonObject));

        return redisUtil.get("strKey");
    }

    @GetMapping("/getCaptcha")
    public void setCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //设置随机字符（大小写字母及数字），验证码长度
        //定义图形验证码的长、宽、验证码字符数、干扰元素个数
        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(140, 50);
        //获取验证码，存放session
        request.getSession().setAttribute("captcha", captcha.getCode());
        log.error("验证码-{}", captcha.getCode());
        //写出到浏览器（Servlet输出）
        final ServletOutputStream outputStream = response.getOutputStream();
        captcha.write(outputStream);
        outputStream.flush();
        outputStream.close();
    }

    @GetMapping("/tree")
    public List<SysRegion> getAreaTree() {
        String tree = redisUtil.get(AREA_TREE_KEY);
        if (StrUtil.isNotBlank(tree)) {

            log.info("ddddddd");
            return JSONUtil.toList(tree, SysRegion.class);
        }

        return regionService.areaTree();
    }

    // todo 特别注意，在Springboot 集成 MP的时候，每次调用都会创建一个 sqlSession。导致 一级缓存无效。看打印可知
    // https://cloud.tencent.com/developer/article/1697591 
    // 但是好像又没有什么意义啊。同一个方法中不会执行，同一个查询两次。实际没有必要，
    // 重点看这个：org.mybatis.spring.SqlSessionTemplate 类。实际上 
    @GetMapping("/testMP")
    @Transactional
    public Resp<Boolean> testMybatisFirstCache() {

        final YearModel byId = yearService.getById(1);
        final YearModel byId1 = yearService.getById(1);
        return Resp.ok(byId1 == byId);

    }

    final String FEED_PREFIX = "Feed:";

    /**
     * feed流分页（redis做）（例子：做一个发一个微博，然后将此微博，都推送给我的粉丝的案例）
     *
     * @param lastId   时间戳
     * @param offset   最后一个元素，重复的个数
     * @param pageSize 每页数
     * @return 流动分页（基于redis zSet 实现）
     */
    public Object getFeedPage(Long lastId, Long offset, Integer pageSize) {

        Long userId = 0L;
        Set<ZSetOperations.TypedTuple<String>> typedTupleSet = redisUtil.zReverseRangeByScoreWithScores(FEED_PREFIX + userId, 0, lastId, offset, pageSize);
        if (CollUtil.isEmpty(typedTupleSet)) {
            return null;
        }
        ArrayList<Long> idList = new ArrayList<>(typedTupleSet.size());
        long minTime = 0;
        long os = 0;
        for (ZSetOperations.TypedTuple<String> tuple : typedTupleSet) {
            // 这里一般是主键
            idList.add(Long.valueOf(Objects.requireNonNull(tuple.getValue())));
            long time = tuple.getScore().longValue();
            if (time == minTime) {
                os++;
            } else {
                minTime = time;
                os = 1;
            }
        }
        // 根据 idList 去查需要的信息 注意，此时根据 list直接in会导致，mysql返回是无序的
        getListByOrder(idList);
        // 返回 信息 + minTime + os
        return null;
    }

    private List<YearModel> getListByOrder(ArrayList<Long> list) {
        String idStr = CollUtil.join(list, ",");
        return yearService.list(Wrappers.<YearModel>lambdaQuery().in(YearModel::getId, list)
                .last("order by field(id,+" + idStr + ")"));
    }

    @GetMapping("/sign")
    public String sign() {
        DateTime date = DateUtil.date();
        Long userId = 0L;
        String prefix = "signCount:";

        String key = prefix + userId + ":" + DateUtil.format(date, DatePattern.SIMPLE_MONTH_PATTERN);
        int dayOfMonth = DateUtil.dayOfMonth(date) - 1;
        Boolean set = redisUtil.bitSet(key, dayOfMonth, true);
        return "签到结果：" + set;
    }

    /**
     * 统计当前用户，本月连续签到最大值
     *
     * @return
     */
    @GetMapping("/signCount")
    public Long signCount() {
        DateTime date = DateUtil.date();
        Long userId = 0L;
        String prefix = "signCount:";

        String key = prefix + userId + ":" + DateUtil.format(date, DatePattern.SIMPLE_MONTH_PATTERN);
        // 从一开始
        int dayOfMonth = DateUtil.dayOfMonth(date);
        List<Long> list = redisUtil.bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtil.isEmpty(list)) {
            return 0L;
        }
        Long num = list.get(0);
        if (ObjectUtil.isNull(num)) {
            return 0L;
        }
        // 注意此 result 是十进制的。思考怎么统计最大的连续签到数
        long count = 0;
        while (true) {
            // 让此十进制数与1做与运算，得到数字的最后一位数字是否为0，
            if ((num & 1) == 0) {
                // 是0未签到，结束
                break;
            } else {
                count++;
            }
            // 把数字右移一位，舍弃最后一位
            num >>>= 1;
        }
        return count;
    }
    
    
    String uv_prefix="uv_prefix:";
    @PostMapping("/uv")
    public String uv(@RequestBody List<String> list){
        redisUtil.hYAdd(uv_prefix+DateUtil.format(DateUtil.date(),DatePattern.PURE_DATE_PATTERN),list.toArray(new String[list.size()]) );
        return "ok";
    }
    @GetMapping("/getUvCount")
    public Long getUvCount(){
        Long size = redisUtil.hYSize(uv_prefix + DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_PATTERN));
        return size;
    }
}
